<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
  
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

  
  <link href="../css/style.css" rel="stylesheet" type="text/css">

</head>
<body>
<h1>4.15. Простые процедуры</h1>
<p class="article">
В этой части учебного курса мы рассмотрим основы создания процедур. Процедура представляет собой код, который может выполняться многократно и к которому можно обращаться из разных частей программы. Обычно процедуры предназначены для выполнения каких-то отдельных, законченных действий программы и поэтому их иногда называют подпрограммами. В других языках программирования процедуры могут называться функциями или методами, но по сути это всё одно и то же.
</p>
<h2>Команды CALL и RET</h2>
<p class="article">
Для работы с процедурами предназначены команды CALL и RET. С помощью команды CALL выполняется вызов процедуры. Эта команда работает почти также, как команда безусловного перехода (JMP), но с одним отличием — одновременно в стек сохраняется текущее значение регистра IP. Это позволяет потом вернуться к тому месту в коде, откуда была вызвана процедура. В качестве операнда указывается адрес перехода, который может быть непосредственным значением (меткой), 16-разрядным регистром (кроме сегментных) или ячейкой памяти, содержащей адрес.
</p>
<p class="article">
Возврат из процедуры выполняется командой RET. Эта команда восстанавливает значение из вершины стека в регистр IP. Таким образом, выполнение программы продолжается с команды, следующей сразу после команды CALL. Обычно код процедуры заканчивается этой командой. Команды CALL и RET не изменяют значения флагов (кроме некоторых особых случаев в защищенном режиме). Небольшой пример разных способов вызова процедуры:
</p>
<pre class="code">
use16                   ;Генерировать 16-битный код
org 100h                ;Программа начинается с адреса 100h
 
    mov ax,myproc
    mov bx,myproc_addr
    xor si,si
 
    call myproc         ;Вызов процедуры (адрес перехода - myproc)
    call ax             ;Вызов процедуры по адресу в AX
    call [myproc_addr]  ;Вызов процедуры по адресу в переменной
    call word [bx+si]   ;Более сложный способ задания адреса ;)
 
    mov ax,4C00h        ;\
    int 21h             ;/ Завершение программы
 
;----------------------------------------------------------------------
;Процедура, которая ничего не делает
myproc:
    nop                 ;Код процедуры
    ret                 ;Возврат из процедуры
;----------------------------------------------------------------------
myproc_addr dw myproc   ;Переменная с адресом процедуры
</pre>
<h2>Ближние и дальние вызовы процедур</h2>
<p class="article">
Существует 2 типа вызовов процедур. Ближним называется вызов процедуры, которая находится в текущем сегменте кода. Дальний вызов — это вызов процедуры в другом сегменте. Соответственно существуют 2 вида команды RET — для ближнего и дальнего возврата. Компилятор FASM автоматически определяет нужный тип машинной команды, поэтому в большинстве случаев не нужно об этом беспокоиться.
В учебном курсе мы будем использовать только ближние вызовы процедур.
</p>
<h2>Передача параметров</h2>
<p class="article">
Очень часто возникает необходимость передать процедуре какие-либо параметры. Например, если вы пишете процедуру для вычисления суммы элементов массива, удобно в качестве параметров передавать ей адрес массива и его размер. В таком случае одну и ту же процедуру можно будет использовать для разных массивов в вашей программе. Самый простой способ передать параметры — это поместить их в регистры перед вызовом процедуры.
</p>
<h2>Возвращаемое значение</h2>
<p class="article">
Кроме передачи параметров часто нужно получить какое-то значение из процедуры. Например, если процедура что-то вычисляет, хотелось бы получить результат вычисления.  А если процедура что-то делает, то полезно узнать, завершилось действие успешно или возникла ошибка. Существуют разные способы возврата значения из процедуры, но самый часто используемый — это поместить значение в один из регистров. Обычно для этой цели используют регистры AL и AX. Хотя вы можете делать так, как вам больше нравится.
</p>
<h2>Сохранение регистров</h2>
<p class="article">
Хорошим приёмом является сохранение регистров, которые процедура изменяет в ходе своего выполнения. Это позволяет вызывать процедуру из любой части кода и не беспокоиться, что значения в регистрах будут испорчены. Обычно регистры сохраняются в стеке с помощью команды PUSH, а перед возвратом из процедуры восстанавливаются командой POP. Естественно, восстанавливать их надо в обратном порядке. Примерно вот так:
</p>
<pre class="code">
myproc:
    push bx             ;Сохранение регистров
    push cx
    push si
    ...                 ;Код процедуры
    pop si              ;Восстановление регистров
    pop cx
    pop bx
    ret                 ;Возврат из процедуры
</pre>
<h2>Пример</h2>
<p class="article">
Для примера напишем процедуру для вывода собщения в рамке и протестируем её работу, выведя несколько сообщений. В качестве параметра ей будет передаватся адрес строки в регистре BX. Строка должна заканчиваться символом ‘$’. Для упрощения процедуры можно разбить задачу на подзадачи и написать соответствующие процедуры. Прежде всего нужно вычислить длину строки, чтобы знать ширину рамки. Процедура get_length вычисляет длину строки (адрес передаётся также в BX) и возвращает её в регистре AX.
Для рисования горизонтальной линии из символов предназначена процедура draw_line. В DL передаётся код символа, а в CX — количество символов, которое необходимо вывести на экран. Эта процедура не возвращает никакого значения. Для вывода 2-х символов конца строки написана процедура print_endline. Она вызывается без параметров и тоже не возвращает никакого значения. Коды символов для рисования рамок можно узнать с помощью таблицы символов кодировки 866 или можно воспользоваться стандартной программой Windows «Таблица символов», выбрав шрифт Terminal.
</p>
<pre class="code">
use16                   ;Генерировать 16-битный код
org 100h                ;Программа начинается с адреса 100h
    jmp start           ;Переход на метку start
;----------------------------------------------------------------------
msg1    db 'Hello!$'
msg2    db 'asmworld.ru$'
msg3    db 'Press any key...$'
;----------------------------------------------------------------------
start:
    mov bx,msg1
    call print_message  ;Вывод первого сообщения
    mov bx,msg2
    call print_message  ;Вывод второго сообщения
    mov bx,msg3
    call print_message  ;Вывод третьего сообщения
 
    mov ah,8            ;Ввод символа без эха
    int 21h
 
    mov ax,4C00h        ;\
    int 21h             ;/ Завершение программы
 
;----------------------------------------------------------------------
;Процедура вывода сообщения в рамке
;В BX передаётся адрес строки
print_message:
    push ax             ;Сохранение регистров
    push cx
    push dx
 
    call get_length     ;Вызов процедуры вычисления длины строки
    mov cx,ax           ;Копируем длину строки в CX
    mov ah,2            ;Функция DOS 02h - вывод символа
    mov dl,0xDA         ;Левый верхний угол
    int 21h
    mov dl,0xC4         ;Горизонтальная линия
    call draw_line      ;Вызов процедуры рисования линии
    mov dl,0xBF         ;Правый верхний угол
    int 21h
    call print_endline  ;Вызов процедуры вывода конца строки
 
    mov dl,0xB3         ;Вертикальная линия
    int 21h
    mov ah,9            ;Функция DOS 09h - вывод строки
    mov dx,bx           ;Адрес строки в DX
    int 21h
    mov ah,2            ;Функция DOS 02h - вывод символа
    mov dl,0xB3         ;Вертикальная линия
    int 21h
    call print_endline  ;Вызов процедуры вывода конца строки
 
    mov dl,0xC0         ;Левый нижний угол
    int 21h
    mov dl,0xC4         ;Горизонтальная линия
    call draw_line
    mov dl,0xD9         ;Правый нижний угол
    int 21h
    call print_endline  ;Вызов процедуры вывода конца строки
 
    pop dx              ;Восстановление регистров
    pop cx
    pop ax
    ret                 ;Возврат из процедуры
 
;----------------------------------------------------------------------
;Процедура вычисления длины строки (конец строки - символ '$').
;В BX передаётся адрес строки.
;Возвращает длину строки в регистре AX.
get_length:
    push bx             ;Сохранение регистра BX
    xor ax,ax           ;Обнуление AX
str_loop:
    cmp byte[bx],'$'    ;Проверка конца строки
    je str_end          ;Если конец строки, то выход из процедуры
    inc ax              ;Инкремент длины строки
    inc bx              ;Инкремент адреса
    jmp str_loop        ;Переход к началу цикла
str_end:
    pop bx              ;Восстановление регистра BX
    ret                 ;Возврат из процедуры
 
;----------------------------------------------------------------------
;Процедура рисования линии из символов.
;В DL - символ, в CX - длина линии (кол-во символов)
draw_line:
    push ax             ;Сохранение регистров
    push cx
    mov ah,2            ;Функция DOS 02h - вывод символа
drl_loop:
    int 21h             ;Обращение к функции DOS
    loop drl_loop       ;Команда цикла
    pop cx              ;Восстановление регистров
    pop ax
    ret                 ;Возврат из процедуры
 
;----------------------------------------------------------------------
;Процедура вывода конца строки (CR+LF)
print_endline:
    push ax             ;Сохранение регистров
    push dx
    mov ah,2            ;Функция DOS 02h - вывод символа
    mov dl,13           ;Символ CR
    int 21h
    mov dl,10           ;Символ LF
    int 21h
    pop dx              ;Восстановление регистров
    pop ax
    ret                 ;Возврат из процедуры
</pre>
<p class="article">
Результат работы программы выглядит вот так:
</p>
<div class="image">
	<img src="../img/4.15_1.png">
</div>
</body>
</html>
